from utils.interval import Interval

from stacking.units import H


def compute_delivery_intervals(warehouse, items, actions, verbose=True, 
                               max_diff=1*H, min_amplitude=1*H, max_amplitude=2*H):
    """Analyzes deliveries on pallets to determine a common delivery interval for the items 
    delivered to the same pallet, provided the interval between two consecutive deliveries on 
    the pallet are not too far apart (at most 'max_diff' apart). The due intervals' amplitude 
    must lie between 'min_amplitude' and 'max_amplitude'."""
    # build a list of delivery actions for each pallet. Also, any items delivered on entrances 
    # (i.e. tables) have their due intervals immediately fixed to an interval starting and 
    # ending at the action's timestamp
    item_map = dict((item.id, item) for item in items)
    deliveries = dict((pallet.id, []) for pallet in warehouse.exits)
    for action in actions:
        if not action.is_delivery:
            continue
        place = warehouse.objects[action.place]
        if place.is_exit:
            deliveries[place.id].append(action)
        else:
            assert place.is_entrance
            for item_id in action.items:
                item = item_map[item_id]
                item.due = Interval(action.meta.timestamp, action.meta.timestamp)
                
    # and now analyze the sequence of deliveries for each individual pallet, creating groups 
    # of items that will have the same delivery interval (equal to the min and max delivery 
    # dates found among a group of items)
    for pallet, actions in deliveries.iteritems():
        for group in delivery_groups(actions, max_diff, max_amplitude):
            min_due = group[ 0].meta.timestamp
            max_due = group[-1].meta.timestamp
            # if the resulting interval is too small, enlarge it around 
            # the center of this interval to make it 'min_amplitude' wide
            if max_due - min_due < min_amplitude:
                mid_due = (min_due + max_due) * 0.5
                min_due  = mid_due - min_amplitude * 0.5
                max_due  = mid_due + min_amplitude * 0.5
            # set the due date interval on all of the group's items
            due = Interval(min_due, max_due)
            for action in group:
                for item_id in action.items:
                    item = item_map[item_id]
                    item.due = due
                    
            if verbose:
                group_items = []
                for action in group:
                    group_items.extend([item_map[item_id] for item_id in action.items])
                print ("Delivery of %d items at %s from %s to %s (amplitude = %s)" % 
                       (len(group_items), pallet, min_due, max_due, max_due - min_due))
                print "\tTotal thickness = %s" % sum(item.thickness for item in group_items)
                print "\t   Total weight = %s" % sum(item.weight    for item in group_items)
                
                
def delivery_groups(deliveries, max_diff, max_amplitude):
    """This function groups the argument delivery actions into lists of actions with close enough 
    delivery date (<= max_diff). The amplitude of a group's delivery interval is limited by 
    'max_amplitude'. When this value is reached, a new group is created."""
    deliveries.sort(key=lambda a: a.meta.timestamp)
    i = 0
    while i < len(deliveries):
        t_start = deliveries[i].meta.timestamp
        t_prev = t_start
        for j in xrange(i + 1, len(deliveries)):
            t = deliveries[j].meta.timestamp
            if t - t_prev > max_diff or t - t_start > max_amplitude:
                yield deliveries[i:j]
                i = j
                break
            t_prev = t
        else:
            yield deliveries[i:]
            return
            
            